// $Id: string_tokenizer.c,v 1.2 2006-08-22 03:08:31 nicolasw Exp $

/* Tokenizer para cadenas. Escrito por Rafael Carrascosa para la catedra de
 * Sistemas Operativos, FaMAF, UNC.
 * Eres libre de modificar,utilizar y distribuir este codigo para cualquier 
 * fin siempre y cuando reconozcas mi participacion en su escritura.
 */

#include "string_tokenizer.h"
#include <string.h>
#include <malloc.h>
#include <assert.h>

/* Este prototipo es un truco sucio para no usar _GNU_SOURCE */
char *strdup (const char *s);

struct sstring_tokenizer_t
{
	char *copy;
	char *separators;
	char *token;
	char *next;
	char erased_separator;
};

/* Invariante de representacion */
#define INV_REP(x) (x != NULL && x->copy != NULL && x->separators != NULL &&\
                    x->token != NULL && x->next != NULL &&\
		    *(x->next) == '\0')

/* Invariantes informales:
 * -next siempre apunta a una posicion que antes tenia un separador a menos 
 * que se encuentre al final de la cadena en cuyo caso apunta al lugar de '\0'.
 * -next SIEMPRE apunta al '\0' que delimita la cadena actual del iterador.
 */

/* Funciones privadas. Ninguna modifica st. */
static char *advance_over_non_separators(const string_tokenizer_t st);
static int belongs_to_separators(const string_tokenizer_t st, const char c);
static char *string_tokenizer_get(const string_tokenizer_t st);

string_tokenizer_t string_tokenizer_create(const char *str,
					   const char *separators)
{
	string_tokenizer_t st = malloc(sizeof(struct sstring_tokenizer_t));
	assert(str != NULL && separators != NULL);
	if(st == NULL) return NULL;
	st->copy = strdup(str);
	st->separators = strdup(separators);
	st->token = st->copy;
	st->next = st->copy;
	if(st->copy == NULL || st->separators == NULL)
	{
		if(st->separators != NULL) free(st->separators);
		if(st->copy != NULL) free(st->copy);
		free(st);
		return NULL;
	}
	st->next = advance_over_non_separators(st);
	st->erased_separator = *(st->next);
	*(st->next) = '\0';
	return st;
}

string_tokenizer_t string_tokenizer_destroy(string_tokenizer_t st)
{
	assert(INV_REP(st));
	free(st->separators);
	free(st->copy);
	free(st);
	return NULL;
}

static char *string_tokenizer_get(const string_tokenizer_t st)
{
	char *answer = NULL;
	char aux[2];
	assert(INV_REP(st));
	assert(!string_tokenizer_is_end(st));
	if(*(st->token) == '\0') 
	{
		/* st->token no esta al final y vale '\0' entonces es porque */
		/* piso a un caracter separador. Por los tanto ese es el     */
		/* token actual que hay de devolver */
		aux[0] = st->erased_separator;
		aux[1] = '\0';
		answer = strdup(aux);
	} else answer = strdup(st->token);
	return answer;
}

char * string_tokenizer_next(string_tokenizer_t st)
{
	char *answer=NULL;
	assert(INV_REP(st));
	assert(!string_tokenizer_is_end(st));

	answer = string_tokenizer_get(st);
	if(answer == NULL) return NULL;

	/* Este if dice que se avanza de a pedasitos: o de token a separator */
	/* o de separator a token */
	if(*(st->token) == '\0')
	{
		/* Este es el caso de separator a token(o a otro separator) */
		if(st->erased_separator != '\0') (st->next)++;
		st->token = st->next;
		st->next = advance_over_non_separators(st);
		st->erased_separator = *(st->next);
		*(st->next) = '\0';
	} else
		/* Este es el caso de token a separator */
		st->token = st->next;

	return answer;
}

int string_tokenizer_is_end(const string_tokenizer_t st)
{
	assert(INV_REP(st));
	return (st->token == st->next && st->erased_separator == '\0');
}

static char *advance_over_non_separators(const string_tokenizer_t st)
{
	char *finder = NULL;
	finder = st->next;
	while(*finder != '\0' &&
	      !belongs_to_separators(st,*finder))
		finder++;
	return finder;
}

static int belongs_to_separators(const string_tokenizer_t st, const char c)
{
	char *finder = NULL;
	finder = st->separators;
	while(*finder != '\0' && *finder != c) finder++;
	if(*finder == '\0') return 0;
	return 1;
}
